#ifndef _LEVEL_C
#define _LEVEL_C

#include "level.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* x = n % y
   ensures that x is between 0 and y */
#define ABSMOD(x,y) ((x)<0?(x)+(y):(x))

typedef struct {
	int i;
	char *s;
} valpair;

#define MAX_BLOCK_BY_NAME 8
#define MAX_I_BY_NAME 4

static valpair blocks_by_name[MAX_BLOCK_BY_NAME] = {
	{BLOCK_WALL,"wall"},
	{BLOCK_HOLESPR,"background"},
	{BLOCK_HOLE,"hole"},
	{BLOCK_GRID,"grid"},
	{BLOCK_CHECK,"check"},
	{BLOCK_JUMP,"jump"},
	{BLOCK_AI_HINT,"hint"},
	{BLOCK_DEFAULT,"default"}};

static valpair i_by_name[MAX_I_BY_NAME] = {
	{BLOCK_I_UP,"up"},
	{BLOCK_I_DOWN,"down"},
	{BLOCK_I_LEFT,"left"},
	{BLOCK_I_RIGHT,"right"}};

level *load_level(char *name)
{
	FILE *f;
	char sprname[256];
	level *l;
	int i,w,h;
	gp_spr **s;
	char tempstr[256];
	printf("Loading level: ");
	f = fopen(name,"r");
	if (f == 0)
	{
		printf("Couldn't open file\n");
		return 0;
	}
	l = malloc(sizeof(level));
	if (l == 0)
	{
		printf("Not enough memory\n");
		fclose(f);
		return 0;
	}
	l->checkpoints = 0;
	l->hints = 0;
	if (fscanf(f,"%s\n%d\n%d\n",sprname,&l->w,&l->h) != 3)
	{
		printf("Bad header\n");
		free(l);
		fclose(f);
		return 0;
	}
	l->map = malloc(4*l->w*l->h);
	if (l->map == 0)
	{
		printf("Not enough memory for map\n");
		free(l);
		fclose(f);
		return 0;
	}
	/* Read block definitions */
	for (i=0;i<MAX_BLOCK;i++)
	{
		l->blocks[i].spr_name[0] = 0;
		l->blocks[i].spr = 0;
		l->blocks[i].flags = l->blocks[i].i = 0;
	}
	l->highest_check = 0;
	l->numhints = 0;
	i = 0; /* Current block */
	/* Should really use stok for this! */
	do {
		fgets(tempstr,255,f);
		if ((strlen(tempstr) > 0) && (tempstr[strlen(tempstr)-1] == 10))
			tempstr[strlen(tempstr)-1] = 0;
		if (strcmp(tempstr,"map") == 0)
			break;
		/* Process string:
		   <char> <spr>[ <flag>]...
		*/
		/* Write block data */
		i = tempstr[0]; /* Block ID */
		h = 2;
		w = 0;
		while ((w < 12) && (tempstr[h] > 32))
			l->blocks[i].spr_name[w++] = tempstr[h++];
		l->blocks[i].spr_name[w] = 0;
		while (tempstr[h] > 32)
			h++;
		while ((tempstr[h]) && (tempstr[h] < 33))
			h++;
		/* Read flags */
		if (tempstr[h] > 32)
		{
			/* i */
			if ((tempstr[h] >= '0') && (tempstr[h] <= '9'))
				l->blocks[i].i = atoi(tempstr+h);
			else for (w=0;w<MAX_I_BY_NAME;w++)
				if (strncmp(tempstr+h,i_by_name[w].s,strlen(i_by_name[w].s)) == 0)
				{
					l->blocks[i].i = i_by_name[w].i;
					break;
				}
			if (l->blocks[i].i) {
				while (tempstr[h] > 32)
					h++;
				while ((tempstr[h]) && (tempstr[h] < 33))
					h++;
			}
		}
		while (tempstr[h] > 32)
		{
			/* Other flags */
			for (w=0;w<MAX_BLOCK_BY_NAME;w++)
				if (strncmp(tempstr+h,blocks_by_name[w].s,strlen(blocks_by_name[w].s)) == 0)
				{
					l->blocks[i].flags |= blocks_by_name[w].i;
					break;
				}
			while (tempstr[h] > 32)
				h++;
			while ((tempstr[h]) && (tempstr[h] < 33))
				h++;
		}
	} while (1);
	/* Verify block definitions */
	l->defaultb = block_find_flag(l,BLOCK_DEFAULT,0);
	if (l->defaultb == -1)
	{
		printf("No default block defined\n");
		fclose(f); free(l->map); free(l); return 0;
	}
	i = block_find_flag(l,BLOCK_GRID,0);
	if (i == -1)
	{
		printf("No grid block defined\n");
		fclose(f); free(l->map); free(l); return 0;
	}
	if (l->blocks[i].i == 0)
	{
		printf("Starting grid has no direction\n");
		fclose(f); free(l->map); free(l); return 0;
	}
	l->background = block_find_flag(l,BLOCK_HOLESPR,0);
	if (l->background == -1)
	{
		printf("No hole block defined\n");
		fclose(f); free(l->map); free(l); return 0;
	}
	/* Read map */
	i = block_find_flag(l,BLOCK_DEFAULT,0);
	for (w=0;w<l->w;w++)
		for (h=0;h<l->h;h++)
			l->map[w+h*l->w] = i;
	w=h=0;
	while ((i=fgetc(f)) != EOF) {
		if (i == 10)
		{
			w=0;
			h++;
			if (h == l->h)
				break;
		}
		else if (l->blocks[i].spr_name[0] == 0)
		{
			printf("Undefined block '%c' used\n",i);
			fclose(f); free(l->map); free(l); return 0;
		}
		else if (w < l->w)
		{
			l->map[w+h*l->w] = i;
			w++;
		}
	}
	fclose(f);
	/* Verify checkpoints, build location list */
	l->highest_check = 0;
	if (block_find(l,block_find_flag(l,BLOCK_GRID,0),0,0) != 0)
	{
		printf("No grid on map\n");
		free(l->map); free(l); return 0;
	}
	while (block_find(l,block_find_flag(l,BLOCK_CHECK,l->highest_check+1),0,0) == 0)
		l->highest_check++;
	if (l->highest_check < 1)
	{
		printf("Map must have at least 1 checkpoint\n");
		free(l->map); free(l); return 0;
	}
	/* Create structure */
	l->checkpoints = malloc(8*(l->highest_check+1));
	if (l->checkpoints == 0)
	{
		printf("Not enough memory for checkpoint list\n");
		free(l->map); free(l); return 0;
	}
	block_find(l,block_find_flag(l,BLOCK_GRID,0),&(l->checkpoints[0]),&(l->checkpoints[1]));
	for (i=1;i<=l->highest_check;i++)
		block_find(l,block_find_flag(l,BLOCK_CHECK,i),&(l->checkpoints[2*i]),&(l->checkpoints[2*i+1]));
	/* Count hints */
	l->numhints = l->highest_check+1;
	for (w=0;w<l->w;w++)
		for (h=0;h<l->h;h++)
			if (l->blocks[l->map[w+h*l->w]].flags & BLOCK_AI_HINT)
				l->numhints++;
	l->hints = malloc(8*l->numhints);
	if (l->hints == 0)
	{
		printf("Not enough memeory for AI hints\n");
		free(l->map); free(l->checkpoints); free(l); return 0;
	}
	w = 0; /* checkpoint number */
	for (i=0;i<l->numhints;i++)
	{
		h = block_find_flag(l,BLOCK_AI_HINT,i);
		if ((i == 0) || (h == -1))
		{
			if (w > l->highest_check)
			{
				printf("Bad hints\n");
				free(l->map);
				free(l->checkpoints);
				free(l->hints);
				free(l);
				return 0;
			}
			l->hints[i*2] = l->checkpoints[w*2];
			l->hints[i*2+1] = l->checkpoints[w*2+1];
			w++;
		}
		else if (block_find(l,h,&(l->hints[i*2]),&(l->hints[i*2+1])))
		{
			printf("Hint %d not found on map\n",i);
			free(l->map);
			free(l->checkpoints);
			free(l->hints);
			free(l);
			return 0;
		}
	}
	printf("OK\n");
	printf("Loading level sprites: ");
	s = gp_spr_paint_loadfile(sprname,&i);
	if ((s == 0) || (i == 0))
	{
		printf("File not found/no sprites\n");
		free(l->map);
		free(l->checkpoints);
		free(l->hints);
		free(l);
		return 0;
	}
	printf("OK\n");
	printf("Converting sprites: ");
	l->sprw = (s[0]->f->getw)(s[0]);
	l->sprh = (s[0]->f->geth)(s[0]);
	for (h=0;h<MAX_BLOCK;h++)
	{
		if (l->blocks[h].spr_name[0] != 0) {
			w = gp_spr_paint_find(s,i,l->blocks[h].spr_name);
			if (w == -1)
				l->blocks[h].spr = 0;
			else if ((s[w]->f->getmt)(s[w]) == GP_MT_NONE)
				l->blocks[h].spr = (gp_spr_pre00.fromspr)(&gp_spr_pre00,s[w],0,0,l->sprw,l->sprh,0,0);
			else
				l->blocks[h].spr = (gp_spr_01.fromspr)(&gp_spr_01,s[w],0,0,l->sprw,l->sprh,0,0);
		}
	}
	gp_spr_ar_delete(s,i);
	for (h=0;h<MAX_BLOCK;h++)
		if ((l->blocks[h].spr_name[0] != 0) && (l->blocks[h].spr == 0))
		{
			printf("Sprite '%s' not found\n",l->blocks[h].spr_name);
			kill_level(l);
			return 0;
		}
	printf("OK\n");
	return l;
}

void kill_level(level *l)
{
	int i;
	if (l == 0)
		return;
	if (l->map)
		free(l->map);
	for (i=0;i<MAX_BLOCK;i++)
		if (l->blocks[i].spr)
			(l->blocks[i].spr->f->delete)(l->blocks[i].spr);
	if (l->checkpoints)
		free(l->checkpoints);
	if (l->hints)
		free(l->hints);
	free(l);
}

int block_find(level *l,int b,int *x,int *y)
{
	int o=0;
	int xx,yy;
	if ((l == 0) || (l->map == 0) || (l->w == 0) || (l->h == 0) || (b == -1))
		return 1;
	if (x == 0)
		x = &xx;
	if (y == 0)
		y = &yy;
	for (*y=0;(*y)<l->h;(*y)++)
		for (*x=0;(*x)<l->w;(*x)++)
			if (l->map[o++] == b)
				return 0;
	return 1;
}

int block_find_flag(level *l,int f,int i)
{
	int j;
	if (l == 0)
		return -1;
	for (j=0;j<MAX_BLOCK;j++)
		if ((l->blocks[j].flags & f) == f)
			if ((i == 0) || (l->blocks[j].i == i))
				return j;
	return -1;
}

void draw_level(level *l,gp_screen *s,int x,int y)
{
	int minx,miny,xx,minxx,i,parax,paray;
	gp_spr *parabg,*origbg;
	gp_screen parascr;
	if ((l == 0) || (s == 0))
		return;
	/* Calculate the blocks we can see */
	x -= s->width/2;
	y -= s->height/2;
	/* Calculate parallax background sprite */
	parax = ABSMOD((x/2) % l->sprw,l->sprw);
	paray = ABSMOD((y/2) % l->sprh,l->sprh);
	origbg = l->blocks[l->background].spr;
	parascr.width = l->sprw*2;
	parascr.height = l->sprh*2;
	parascr.ps = parascr.mt = parascr.gap = 0;
	parascr.bank0 = parascr.bank1 = malloc(l->sprw*l->sprh*4);
	(origbg->f->plot)(origbg,0,0,&parascr);
	(origbg->f->plot)(origbg,l->sprw,0,&parascr);
	(origbg->f->plot)(origbg,0,l->sprh,&parascr);
	(origbg->f->plot)(origbg,l->sprw,l->sprh,&parascr);
	parabg = (gp_spr_pre00.fromscreen)(&gp_spr_pre00,&parascr,l->sprw-parax,l->sprh-paray,l->sprw,l->sprh,0,0);
	free(parascr.bank0);
	/* Resume calculating visible screen area... */
	minx = x/l->sprw; /* Block visible in top-left of screen */
	miny = y/l->sprh;
	x = (l->sprw-1)-(x % l->sprw);
	y = (l->sprh-1)-(y % l->sprh);
	while (x > 0)
	{
		x -= l->sprw;
		minx--;
	}
	while (y > 0)
	{
		y -= l->sprh;
		miny--;
	}
	minx++;
	miny++;
	/* Now minx and miny are the blocks visible in the top-left of the screen, and x and y are the locations to draw them */
	while (y < s->height) {
		xx = x;
		minxx = minx;
		while (xx < s->width) {
			if ((minxx < 0) || (miny < 0) || (minxx >= l->w) || (miny >= l->h))
				i = l->defaultb;
			else
				i = l->map[minxx+l->w*miny];
			if (l->blocks[i].spr)
			{
				if ((i == l->background) || ((l->blocks[i].spr->f->getmt)(l->blocks[i].spr) != 0)) /* Is the background block or is transparent */
					(parabg->f->plot)(parabg,xx,y,s);
				if (i != l->background)
					(l->blocks[i].spr->f->plot)(l->blocks[i].spr,xx,y,s);
			}
			else /* This shouldn't happen! */
				(parabg->f->plot)(parabg,xx,y,s);
			xx += l->sprw;
			minxx++;
		}
		y += l->sprh;
		miny++;
	}
	(parabg->f->delete)(parabg);
}

int map_read(level *l,int x,int y)
{
	if (l == 0)
		return -1;
	if ((x < 0) || (x >= l->w) || (y < 0) || (y >= l->h))
		return l->defaultb;
	return l->map[x+y*l->w];
}

f1616 ground_height(level *l,int b,f1616 xofs,f1616 yofs)
{
	if (l->blocks[b].flags & BLOCK_HOLE)
		return 0x80000000; /* Negative infinity */
	if ((l->blocks[b].flags & BLOCK_JUMP) == 0)
		return 0;
	switch (l->blocks[b].i) {
	case BLOCK_I_UP:
		xofs = (l->sprh << 16)-yofs;
		break;
	case BLOCK_I_DOWN:
		xofs = yofs;
		break;
	case BLOCK_I_LEFT:
		xofs = (l->sprw << 16)-xofs;
		break;
	case BLOCK_I_RIGHT:
		xofs = xofs;
		break;
	}
	return f1616_mul(xofs >> 9,GRAVITY); /* Max ramp height is size*gravity/512 */
}

#endif
